<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button message="后盾人" class="red">后盾人</button>
    <button message="hdcms">hdcms</button>
  </body>
</html>

<script>
  // 原型与原型链还有继承
  let arr = ["jason"];
  // console.log(arr.concat("houdunren.com"));

  let hd = {};
  let mr = {};
  // console.log(Object.getPrototypeOf(hd) == Object.getPrototypeOf(mr)); //同一个原型父亲

  // 完全数据字典对象
  let hd1 = Object.create(null, {
    name: {
      value: "后盾人",
    },
  });
  // console.log(hd1);

  // 原型方法与对象方法的优先级
  let hd2 = {
    show() {
      console.log("jason");
    },
  };

  hd2.__proto__.show = function () {
    // 往原型对象上添加方法给实例用
    // console.log("我是proto的方法");
  };
  // hd2.show();

  // 函数拥有多个长辈 new String, new Number, new Boolean, new RegExp
  // __proto__ 服务于对象，利用对象直接调用
  function User(name) {
    this.name = name;
  }
  User.__proto__.view = function () {
    // console.log("User function view method");
  };
  // User.view();

  // 这种是服务函数实例化对象的
  User.prototype.show = function () {
    // console.log("我在prototype中添加了show方法");
    // console.log(this.name);
  };
  let hd3 = new User();
  // hd3.view(); 这种情况是调用不了的，因为你new出来的就会在prototype中找，可是你没有所以会报错
  // hd3.show();

  // 原型关系详解与属性继承关系
  let hd4 = new Object();
  hd4.hobbyb = "lanqiu";
  Object.prototype.haha = function () {
    // console.log("我在原型链的顶端");
  };

  // console.log(Object.prototype.__proto__ == Object.prototype);

  // console.log(hd3.__proto__ == User.prototype);

  // console.log(User.prototype.__proto__ == Object.prototype);

  let func = new Function();
  // console.log(Function.prototype.__proto__ == Object.prototype);

  // console.log(Function.prototype == Object.__proto__);
  // console.log(Function.__proto__ == Function.prototype);

  Array.prototype.show = function () {
    // console.log("后盾人");
  };

  let reg = /a/i; // new RegExp
  // console.log(reg.__proto__ == RegExp.prototype);

  let arr1 = [];

  arr1.__proto__.show();

  let hd5 = { name: "hd" };
  let parent = {
    name: "parent",
    show() {
      // console.log("parent method: " + this.name); // 谁调用show，this就是谁
    },
  };
  Object.setPrototypeOf(hd5, parent); // 将hd的原型设置为parent
  hd5.show();

  User.prototype.view = function () {
    // console.log("我是prototype中的view方法");
  };

  // 这样子会报错，因为你已经改变了对象的指向了。指向了一个新的地址，而且该对象还没有constructor函数
  // User.prototype = {
  // constructor: User,
  // show() {},
  // };

  let zs = new User.prototype.constructor(); // 通过原型找到它的构造函数
  // console.log(zs);
  zs.view();

  let hd6 = new User("后盾人");

  function createByObject(obj, ...args) {
    // const constructor = obj.__proto__.constructor();

    const constructorObj = obj.__proto__.constructor;

    return new constructorObj(...args);
  }

  let xj = createByObject(hd6, "向军");
  xj.show();

  // console.log(arr.__proto__.__proto__ == Object.prototype); // true

  let a = { name: "a" };
  let b = { name: "b" };
  let c = {
    name: "c",
    show() {
      console.log(this.name);
    },
  };
  // 统一将c设置为a和b的原型父亲，所以只需要关注c就可以了，给c添加任何东西a，b都可以用
  Object.setPrototypeOf(a, c);
  Object.setPrototypeOf(b, c);
  // a.show();
  // b.show();

  // 判断构造函数是否在原型链上
  function A() {}
  function B() {}
  function C() {}
  let cc = new C();
  B.prototype = cc;
  let bb = new B();
  A.prototype = bb;
  let aa = new A();
  // console.log(aa instanceof A); // true
  // console.log(aa instanceof C); // true
  // console.log(aa instanceof B); // true
  // console.log(bb instanceof A); // false

  let aaa = {};
  let bbb = {};
  let ccc = {};

  // 将bbb的原型对象设置为ccc
  Object.setPrototypeOf(bbb, ccc);
  Object.setPrototypeOf(aaa, bbb);
  // isPrototypeOf判断 a的原型链当中是否存在b对象
  // console.log(bbb.isPrototypeOf(aaa)); // true
  // console.log(ccc.isPrototypeOf(bbb)); // true
  // console.log(ccc.isPrototypeOf(aaa)); // true

  let a1 = { url: "jason666" };
  let b1 = { name: "houdunrne" };

  Object.setPrototypeOf(a1, b1);

  // console.log(a1.hasOwnProperty("url")); // 只检查a1这个对象有没有url这个属性，不会沿着原型链去找

  for (const key in a1) {
    // 只遍历a1这个对象的属性
    if (Object.hasOwnProperty.call(a1, key)) {
      // console.log(key);
    }
  }

  let data = {
    data1: [1, 2, 3, 4, 5],
  };

  Object.setPrototypeOf(data, {
    // max() {
    //   return this.data1.sort((a, b) => b - a)[0];
    // },

    max(data) {
      return data.sort((a, b) => b - a)[0];
    },
  });

  let xj1 = {
    lessons: { js: 99, php: 30, node: 60, linux: 100 },
    get data1() {
      return Object.values(this.lessons);
    },
  };
  // 借助apply或者是call来使用其他原型链上的方法
  // console.log(data.max.apply(xj1, xj1.data1));

  // console.log(data.max.call(null, Object.values(xj1.lessons)));

  // 这里的apply传参可以理解为分散传，你直接放入一个数组他会展开
  // console.log(Math.max.apply(null, data.data1));
  // 而call，若不使用...运算符他会当作一个参数来看待，显然是不正确的
  // console.log(Math.max.call(null, ...data.data1));

  let docs = document.querySelectorAll("button");

  // DOM节点借用Array原型方法
  let arr2 = [1, 2];
  arr2.filter(function (item) {
    // console.log(item);
  });
  // 这里call需要传递一个参数，传递的参数是回调函数
  let docFilter = arr2.filter.call(docs, (item) => {
    return item.hasAttribute("class");
  });
  // console.log(docFilter[0].innerHTML);

  // 当你同一个方法会被多个实例用，你可以尝试写在prototype上
  function Faker(name) {
    this.name = name;
  }

  Faker.prototype.show = function () {
    // console.log(this.name);
  };

  let lisi = new Faker("李四");
  let xj2 = new Faker("向军");
  // console.log(lisi, xj2); // 我们可以看到两个对象都有show这个方法，这样子比较浪费内存，没必要，可以尝试在prototype上添加show方法
  lisi.show();

  // this永远指向调用者
  let hd7 = {
    name: "66",
  };
  let checkout6 = {
    name: "刘晓曼",
    show() {
      console.log(this.name);
    },
  };
  Object.setPrototypeOf(hd7, checkout6);
  // checkout6.show(); // checkout6调用的，this指向该对象
  // hd7.show();

  // 定义对象的原型，不能获取
  let User11 = {
    show() {
      return this.name;
    },
  };

  // prototype
  let hd11 = Object.create(User11, {
    name: {
      value: "houdunren",
    },
  });

  let hd8 = { name: "liuxioaman" };
  hd8.__proto__ = User11;
  // console.log(hd8.__proto__);

  // __proto__原来是属性访问器
  let hd9 = {
    action: {},
    get proto() {
      return this.action;
    },
    set proto(obj) {
      if (obj instanceof Object) this.action = obj;
    },
  };

  hd9.proto = "abc"; // 这样子是设置不了的，因为set 做了限制
  hd9.proto = { name: "jason" };
  // console.log(hd9.proto);

  // 如果硬要设置可以使用以下方法，将hd9的原型对象设置为null，这样子就找不到get __proto__对象来
  let hd10 = Object.create(null);
  hd10.name = 777;
  // console.log(hd10);

  // 原型的基础，而不是改变构造函数的原型

  function Useraa() {}
  Useraa.prototype.name = function () {
    console.log("user.name");
  };

  function Admin() {}
  // 无顺序问题
  // Admin.prototype.__proto__ = Useraa.prototype;

  Admin.prototype = Object.create(Useraa.prototype); // 这种方法相当于把admin的prototype指到Useraa.prototype，实则是创建了一个新的对象，但是这个时候你是完全的改变了一个对象，你应该很自然地给他添加上constructor属性才行，保证构造函数不要丢失, 但是这样子设置的constructor是可遍历的，我们应该不用遍历它，因此需要换一种方式设置
  // Admin.prototype.constructor = Admin;

  Object.defineProperty(Admin.prototype, "constructor", {
    value: Admin,
    enumerable: false,
  });

  // 这个时候可以打印看看constructour这属性的详细情况
  console.log(Object.getOwnPropertyDescriptors(Admin.prototype));

  Admin.prototype.role = function () {
    console.log("admin.roel");
  };

  function Member() {}
  Member.prototype.__proto__ = Useraa.prototype;
  Member.prototype.role = function () {
    console.log("Member.role");
  };

  let aAdmin = new Admin(); // 一样可以使用 user中的name
  let bAdmin = new aAdmin.__proto__.constructor();
  // console.log("My name is bAdmin", bAdmin);

  let aMember = new Member(); // 一样可以使用。user中的name
  // aMember.role(); // 这时候就出现了问题，他们三个是同一个原型对象，所以不可以这样子，后面的role方法会被覆盖，所以输出的事Member.role

  // 方法的重写与父级属性访问，很简单就是就近原则
  // 面向对象的多态 这种就是多态度，函数调用一次就可以有多种反应，只需要调用show()就会自动调用description方法

  function User22() {} // 父类
  User22.prototype.show = function () {
    this.description();
  };

  function Admin1() {}
  Admin1.prototype = Object.create(User22.prototype);
  Object.defineProperty(Admin1.prototype, "constructor", {
    value: Admin1,
    enumerable: false,
  });

  Admin1.prototype.description = function () {
    console.log("我是管理员");
  };

  function Member1() {}
  Member1.prototype = Object.create(User22.prototype);
  Member1.prototype.description = function () {
    console.log("我是Member1");
  };

  function Enterprise1() {}
  Enterprise1.prototype = Object.create(User22.prototype);
  Enterprise1.prototype.description = function () {
    console.log("我是Enterprise1");
  };

  for (const obj of [new Admin1(), new Member1(), new Enterprise1()]) {
    // obj.show();
  }

  // 使用父类构造函数初始值属性，只需要改变this指向即可完成
  function User33(name, age) {
    this.name = name;
    this.age = age;
  }
  User33.prototype.show = function () {
    console.log(this.name, this.age);
  };

  // Admin
  function Admin2(...args) {
    User33.apply(this, args);
  }
  Admin2.prototype = Object.create(User33.prototype);
  let xj3 = new Admin2("向军", 10);
  // xj3.show();

  // 会员
  function Member2(...args) {
    User33.apply(this, args);
  }
  Member2.prototype = Object.create(User33.prototype);
  let Me3 = new Member2("会员VIp", 3);
  // Me3.show();

  // 使用原型的工厂函数封装继承, 只需要调用即可
  function extend(sub, sup) {
    sub.prototype = Object.create(sup.prototype);
    Object.defineProperty(sub.prototype, "constructor", {
      value: sub,
      enumerable: false,
    });
  }

  function Lijahui(...args) {
    User33.apply(this, args);
  }
  extend(Lijahui, User33);
  let test = new Lijahui("jason", 66);
  // test.show();

  // 对象工厂派生对象并实现继承
  function admin1(name, age) {
    // 创建一个新的对象，将新的原型对象指向User33.prototype
    // 然后就有show的方法
    const instance = Object.create(User33.prototype);
    User33.call(instance, name, age);
    return instance;
  }

  let obj = admin1("jason", 1000);
  obj.show();
</script>
